/**
 * Created by yuzhang on 2018/5/9.
 */
import {send, sendLogin, sendAnyThing} from './send'
import * as events from './eventGenerators'
import {forIn, guid, isFunc, verifyDuration, wrapObject} from './utils'
import {trackCWMHOC} from './trackHOCs'
import {durationModule} from './module'
import { AppState, NativeModules, Platform, NativeEventEmitter } from "react-native";
import moment from "moment/moment";

/**
 * @param type    操作类型：'delete' 删除 'save' 存储
 * @param ID      操作数据键名ID：唯一标识
 * @param jsonObj 操作数据：类型存储时传，数据持久化在app中
 */
function _storageFun(type, ID, data) {}
let _intervalTimer
let _printLog = false
let _timerLog = false

// 包装待发送的数据，并不会删除数据，
export function wrapDurationEndData (ID, extraData) {
    // 先预检计算时长
    const info = durationModule.precheckEnd(ID)
    let data = undefined
    let sendData = undefined
    if (info) {
        let extra = wrapObject(extraData)
        // data 里面记得要塞 type 表明自身是什么类型的数据，好对应不同的日志处理和发送形式
        data = {...info.extra, ...extra, id: info.ID, type: info.type, startTime: info.startTime, endTime: info.endTime}
        switch (data.type) {
            case 'duration': {
                sendData = events.duration(info.name || ID, durationModule.getSessionID(), verifyDuration(info.duration), data)
            }
                break;
            case 'background': {
                sendData = events.background(info.name || ID, durationModule.getSessionID(), verifyDuration(info.duration), data)
            }
                break;
            case 'loading': {
                let utcOffset = moment().utcOffset() / 60
                data = {
                    ...data,
                    categoryKey: 'sendUserId',
                    timeZone: 'GMT' + (utcOffset >= 0 ? `+${utcOffset}` : utcOffset),
                }
                sendData = events.loading(verifyDuration(info.duration), data)
            }
                break;
            default:
                break;
        }
    }
    return sendData;
}

// 触发最终的发送行为，会生成日志
export const sendWrapDurationData = (ID, extraData) => {
    _storageFun('delete', ID) // 删除App持久化预检缓存
    let sendData = wrapDurationEndData(ID, extraData)
    if (!sendData) {
        if (__DEV__ && _printLog) {
            console.log(ID + '(old)-->?')
        }
        let dState = durationModule.getState()
        let dStateKeys = Object.keys(dState)
        for (let i = 0; i < dStateKeys.length; i++) {
            let itemID = dStateKeys[i]
            let item = durationModule.getModule(itemID)
            if (item.extra && item.extra.originTrackId && (item.extra.originTrackId.indexOf(ID) !== -1)) {
                if (__DEV__ && _printLog) {
                    console.log(ID + '(old)-->(new)' + itemID)
                }
                ID = itemID
                _storageFun('delete', ID) // 删除App持久化预检缓存
                sendData = wrapDurationEndData(ID, extraData)
                break
            }
        }
    }
    if (sendData) {
        // 生成发送日志，并删除缓存
        durationModule.deleteTrackID(ID) // sendData 已经计算过时长了，计时结束，删除对象
        let type = sendData.type || sendData.eventData?.type
        switch (type) {
            case 'duration':
            case 'background': {
                send(sendData)
            }
                break;
            case 'loading': {
                sendAnyThing(sendData)
            }
                break;
            default:
                break;
        }
    }
}

export function setOptions (options) {
    NativeModules.YuanXinTrack.setOptions && NativeModules.YuanXinTrack.setOptions(options)
}

/**
 * @param data kit登录成功返回的信息  // TODO：耦合了kit，Not Good
 */
export function loginSuccess(data) {
    let event = events.loginSuccess('conventional', data.open_id)
    durationModule.setSessionID(event.logLot);
    sendLogin(event)
}

// 记住密码自动登录
export function loginSuccessAuto(data) {
    let event = events.loginSuccess('remember', data.open_id)
    if(durationModule.getSessionID()){
        event.logLot = durationModule.getSessionID();
    }
    durationModule.setSessionID(event.logLot);
    sendLogin(event)
}

// 登录状态下后台运行重回app
export function loginBackToFront(data) {
    let event = events.loginSuccess('background', data.open_id)
    if(durationModule.getSessionID()){
        event.logLot = durationModule.getSessionID();
    }
    durationModule.setSessionID(event.logLot);
    sendLogin(event)
}

/**
 *
 * @param name 必填，发送统计信息的name
 * @param extraData 选填，要发送的额外信息，尽量不要用 end 时传，能提前知道的信息尽量在 start 时候传入，避免进程被杀死时恢复不了数据的问题
 * @param ID   选填，唯一标识，如果没有则会自动生成
 *
 * @return ID  传入的 ID 或 自动生成的唯一ID
 */
export const durationStart = (name, extraData, ID) => {
    const info = durationModule.durationStart(name, 'duration', ID)
    let extra = wrapObject(extraData)
    info.extra = extra // 记录自定义额外数据
    return info.ID
}

/**
 *
 * @param ID 必填，durationStart 生成的唯一ID
 * @param extraData 选填，要发送的额外信息，尽量不要用 end 时传，能提前知道的信息尽量在 start 时候传入，避免进程被杀死时恢复不了数据的问题
 */
export const durationEnd = (ID, extraData) => {
    sendWrapDurationData(ID, extraData)
}

// 注意与 durationModule.backgroundStart 的区别
// extraData 选填，要发送的额外信息，尽量不要用 end 时传，能提前知道的信息尽量在 start 时候传入，避免进程被杀死时恢复不了数据的问题
export const backgroundStart = (name, extraData, ID) => {
    const info = durationModule.durationStart(name, 'background', ID)
    let extra = wrapObject(extraData)
    info.extra = extra // 记录自定义额外数据
    return info.ID
}

// 注意与 durationModule.backgroundEnd 的区别
// extraData 选填，要发送的额外信息，尽量不要用 end 时传，能提前知道的信息尽量在 start 时候传入，避免进程被杀死时恢复不了数据的问题
export const backgroundEnd = (ID, extraData) => {
    sendWrapDurationData(ID, extraData)
}

/**
 *
 * 用定时器间隔固定时间预检测是否进程被杀死，目前进程被刹死没有办法在 js 中监测到，只能用心跳测试来探测，尽量降低误差。
 * @param fun 持久化函数(type: 操作类型 'delete' 删除 'save' 存储, ID: 存储数据键名, jsonObj: 存储数据对象)
 * @param interval 间隔时间，单位毫秒，默认间隔 1 秒
 */
export const startPrecheckTimer = (fun, interval = 1000, log = false, timerLog = false) => {
    _printLog = log
    _timerLog = timerLog
    durationModule._printLog = _printLog
    if (isFunc(fun)) {
        _storageFun = fun
    }
    _intervalTimer = setInterval(() => {
        forIn(durationModule.getState(), item => {
            precheckEnd(item.ID)
        })
    }, interval)
}

// 用定时器间隔固定时间预检测是否进程被杀死，每次把待发送数据暂存起来，避免被杀死时丢失惨重。
// 预检不会生成日志，只是备份
export const precheckEnd = (ID) => {
    // 先预检计算时长
    const info = durationModule.precheckEnd(ID)
    if (info) {
        // 预检时先假设此时是后台结束时间，第二个参数 finished 不传，默认 false 预检不是真实的 end 所以不真正触发结束
        // 这意味着如果是后台时长类型，后续依然可以更新后台时长，如果是前台时长，说明数组中最后一个后台时长已经结束过了，不会再更新其后台时长
        durationModule.backgroundEnd(ID)
        let sendData = wrapDurationEndData(ID)
        _storageFun('save', ID, sendData)
        if (__DEV__ && _printLog && _timerLog) {
            console.log('埋点数据发送预检~~存活备份: ', sendData)
        }
    }
}

// 新增一个区分页面加载时长的方法
// extraData 选填，要发送的额外信息，尽量不要用 end 时传，能提前知道的信息尽量在 start 时候传入，避免进程被杀死时恢复不了数据的问题
export const loadingStart = (name, extraData, ID) => {
    const info = durationModule.durationStart(name, 'loading', ID)
    let extra = wrapObject(extraData)
    info.extra = extra // 记录自定义额外数据
    return info.ID
}

// categoryKey 是用来归类的字段，比如'userID'
// extraData 选填，要发送的额外信息，尽量不要用 end 时传，能提前知道的信息尽量在 start 时候传入，避免进程被杀死时恢复不了数据的问题
export const loadingEnd = (ID, extraData) => {
    sendWrapDurationData(ID, extraData)
}

export const deleteTrackID = (ID) => {
    durationModule.deleteTrackID(ID)
}

// 记录模块停留时间
export const duration = moduleName => InnerComponent => {
    const refName = InnerComponent.prototype.constructor.name
    const name = moduleName || refName
    const ID = `${name}${guid()}` // 这个对于组件(类)来说是唯一的，组件的多个实例中这个值都是相同的
    const methodHooksFunc = function (instance) { // instance: 组件(类)生成的实例
        return {
          componentWillMount() {
            // durationStart(name, null, ID) // 组件的多个实例中这个ID是一样的，所以不能这么写，同样会区分不开
            instance[ID] = durationStart(name) // 每个实例用这个唯一ID作为一个属性来存储生成的ID就没问题了
          },
          componentWillUnmount() {
            durationEnd(instance[ID])
          }
        }
    }
    return trackCWMHOC(methodHooksFunc)(InnerComponent)
}

class BackgroundModule {
    // 整个过程相当于在一个页面的停留时间表示为分段形式：￣___￣￣￣￣￣___
    _handleAppStateChange = (nextAppState) => {
        if (nextAppState.match(/inactive|background/) && this.appState === 'active') {
            if (__DEV__ && _printLog) {
                console.log('before 将前台模式标记为后台模式 state:' + JSON.stringify(durationModule.getState()))
            }
            forIn(durationModule.getState(), item => {
                // 将前台模式标记为后台模式
                durationModule.backgroundStart(item.ID) // 不管是什么类型，先记录后台开始时间
                if (item.type === 'duration') { // 如果是前台时长类型
                    /*
                    durationEnd(item.ID) // 结束所有的记录(会生成日志)
                    backgroundStart(item.name, item.extra, item.ID); // 标记成后台模式，重新生成所有的记录(全新的)，但是用的相同的 ID
                     */
                    /**
                     * 新需求：
                     * 1、App使用时长：从登录App到App退出登录（包括直接退出App、杀死进程）的访问时间间隔总和（去掉后台运行时间）
                     * 2、事件访问时长：从访问事件到退出事件的访问时间间隔总和（去掉后台运行时间）
                     * */
                    // 恢复原来的一个逻辑：退到后台结束所有的记录
                    durationEnd(item.ID) // 结束所有的记录(会生成日志)
                    // 保持原来的一个逻辑：记录后台停留时间，开始后台计时
                    // extraData 里记录原始TrackID(前台模式)
                    // 新创建一个后台时长类型的记录(全新的)，自身使用新的 ID
                    backgroundStart(item.name, {...item.extra, originTrackId: item.ID + ',' + item.extra.originTrackId, originTrackName: item.name});
                }
            })
            if (__DEV__ && _printLog) {
                console.log('after 将前台模式标记为后台模式 state:' + JSON.stringify(durationModule.getState()))
            }
        }
        if (nextAppState === 'active' && this.appState.match(/inactive|background/)) {
            if (__DEV__ && _printLog) {
                console.log('before 将后台模式标记为前台模式 state:' + JSON.stringify(durationModule.getState()))
            }
            // 接着
            forIn(durationModule.getState(), item => {
                // 将后台模式标记为前台模式
                durationModule.backgroundEnd(item.ID, true) // 不管是什么类型，先记录后台结束时间，第二个参数 finished 传 true 表示计时完全结束
                // 结束后台时长类型记录
                if (item.type === 'background') { // 如果是后台时长类型
                    /*
                    backgroundEnd(item.ID); // 结束所有的记录(会生成日志)，这些记录全是标记为后台模式的
                    durationStart(item.name, item.extra, item.ID) // 标记成前台模式，重新生成所有的记录(全新的)，但是用的相同的 ID
                     */
                    // 保持原来的一个逻辑：记录后台停留时间，结束后台计时
                    backgroundEnd(item.ID); // 结束所有的记录(会生成日志)，这些记录全是标记为后台模式的
                    // 恢复原来的一个逻辑：回到前台生成新的不同的日志
                    durationStart(item.name, item.extra) // 标记成前台模式，重新生成所有的记录(全新的)，用新的 ID
                }
                // 紧接着存活备份一次日志记录
                precheckEnd(item.ID)
            })
            if (__DEV__ && _printLog) {
                console.log('after 将后台模式标记为前台模式 state:' + JSON.stringify(durationModule.getState()))
            }
        }
        this.appState = nextAppState
    }

    constructor() {
        this.appState = AppState.currentState
        if(Platform.OS=== 'ios'){
            AppState.addEventListener('change', this._handleAppStateChange)
        }else{
            const eventEmitter = new NativeEventEmitter(NativeModules.YuanXinTrack);
            this.eventListener = eventEmitter.addListener('appCurrentStatus', (event) => {
                if(event && event.appStatus){
                    this._handleAppStateChange(event.appStatus)
                }
            });
        }
    }
}

export const backgroundModule = new BackgroundModule()


// track class内部方法的执行
export const func = getMethodHooks => InnerComponent => {
    let ref = InnerComponent.prototype
    const methodHooksFunc = function (instance) { // instance: 组件(类)生成的实例
      const methodHooks = getMethodHooks(instance)
      // 对非函数类型methodHooks包装函数
      for (let key of Object.keys(methodHooks)) {
        if (isFunc(methodHooks[key])) {
          continue
        }
        let content = methodHooks[key]
        methodHooks[key] = function () {
          send(events.func(ref.constructor.name + '@' + key, durationModule.getSessionID(), content))
        }
      }
      return methodHooks
    }

    return trackCWMHOC(methodHooksFunc)(InnerComponent)
}
